/*
 * Copyright 2006-2009, 2023, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Stephan Aßmus <superstippi@gmx.de>
 *		Zardshard
 */

#include "TransformBoxStates.h"

#include <math.h>

#include <Catalog.h>
#include <Cursor.h>
#include <Locale.h>
#include <View.h>

#include "cursors.h"
#include "support.h"
#include "TransformBox.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Icon-O-Matic-TransformationBoxStates"


using namespace TransformBoxStates;


DragState::DragState(TransformBox* parent)
	:
	fOrigin(0.0, 0.0),
	fParent(parent)
{
}


const char*
DragState::ActionName() const
{
	return B_TRANSLATE("Transformation");
}


void
DragState::_SetViewCursor(BView* view, const uchar* cursorData) const
{
	BCursor cursor(cursorData);
	view->SetViewCursor(&cursor);
}


// #pragma mark - DragCornerState


DragCornerState::DragCornerState(TransformBox* parent, uint32 corner)
	:
	DragState(parent),
	fCorner(corner)
{
}


void
DragCornerState::SetOrigin(BPoint origin)
{
	fOldXScale = fParent->LocalXScale();
	fOldYScale = fParent->LocalYScale();

	fOldOffset = fParent->Translation();

	// copy the matrix at the start of the drag procedure
	fMatrix.reset();
	fMatrix.multiply(agg::trans_affine_scaling(fOldXScale, fOldYScale));
	fMatrix.multiply(agg::trans_affine_rotation(fParent->LocalRotation() * M_PI / 180.0));
	fMatrix.multiply(agg::trans_affine_translation(fParent->Translation().x,
		fParent->Translation().y));

	double x = origin.x;
	double y = origin.y;
	fMatrix.inverse_transform(&x, &y);
	origin.x = x;
	origin.y = y;

	BRect box = fParent->Box();
	switch (fCorner) {
		case LEFT_TOP_CORNER:
			fXOffsetFromCorner = origin.x - box.left;
			fYOffsetFromCorner = origin.y - box.top;
			fOldWidth = box.left - box.right;
			fOldHeight = box.top - box.bottom;
			origin.x = box.right;
			origin.y = box.bottom;
			break;
		case RIGHT_TOP_CORNER:
			fXOffsetFromCorner = origin.x - box.right;
			fYOffsetFromCorner = origin.y - box.top;
			fOldWidth = box.right - box.left;
			fOldHeight = box.top - box.bottom;
			origin.x = box.left;
			origin.y = box.bottom;
			break;
		case LEFT_BOTTOM_CORNER:
			fXOffsetFromCorner = origin.x - box.left;
			fYOffsetFromCorner = origin.y - box.bottom;
			fOldWidth = box.left - box.right;
			fOldHeight = box.bottom - box.top;
			origin.x = box.right;
			origin.y = box.top;
			break;
		case RIGHT_BOTTOM_CORNER:
			fXOffsetFromCorner = origin.x - box.right;
			fYOffsetFromCorner = origin.y - box.bottom;
			fOldWidth = box.right - box.left;
			fOldHeight = box.bottom - box.top;
			origin.x = box.left;
			origin.y = box.top;
			break;
	}
	DragState::SetOrigin(origin);
}


void
DragCornerState::DragTo(BPoint current, uint32 modifiers)
{
	double x = current.x;
	double y = current.y;
	fMatrix.inverse_transform(&x, &y);

	double xScale = 1.0;
	double yScale = 1.0;
	BPoint translation(0.0, 0.0);
	switch (fCorner) {
		case LEFT_TOP_CORNER:
		case RIGHT_TOP_CORNER:
		case LEFT_BOTTOM_CORNER:
		case RIGHT_BOTTOM_CORNER:
			x -= fOrigin.x;
			y -= fOrigin.y;
			if (fOldWidth != 0.0)
				xScale = (x - fXOffsetFromCorner) / (fOldWidth);
			if (fOldHeight != 0.0)
				yScale = (y - fYOffsetFromCorner) / (fOldHeight);
			// constrain aspect ratio if shift is pressed
			if (modifiers & B_SHIFT_KEY) {
				if (fabs(xScale) > fabs(yScale))
					yScale = yScale > 0.0 ? fabs(xScale) : -fabs(xScale);
				else
					xScale = xScale > 0.0 ? fabs(yScale) : -fabs(yScale);
			}
			translation.x = fOrigin.x - fOrigin.x * xScale;
			translation.y = fOrigin.y - fOrigin.y * yScale;
			break;
	}
	x = translation.x;
	y = translation.y;
	fMatrix.transform(&x, &y);
	translation.x = x;
	translation.y = y;

	fParent->SetTranslationAndScale(translation, xScale * fOldXScale, yScale * fOldYScale);
}


void
DragCornerState::UpdateViewCursor(BView* view, BPoint current) const
{
	float rotation = fmod(360.0 - fParent->ViewSpaceRotation() + 22.5, 180.0);
	bool flipX = fParent->LocalXScale() < 0.0;
	bool flipY = fParent->LocalYScale() < 0.0;
	if (rotation < 45.0) {
		switch (fCorner) {
			case LEFT_TOP_CORNER:
			case RIGHT_BOTTOM_CORNER:
				if (flipX) {
					_SetViewCursor(view, flipY
						? kLeftTopRightBottomCursor : kLeftBottomRightTopCursor);
				} else {
					_SetViewCursor(view, flipY
						? kLeftBottomRightTopCursor : kLeftTopRightBottomCursor);
				}
				break;
			case RIGHT_TOP_CORNER:
			case LEFT_BOTTOM_CORNER:
				if (flipX) {
					_SetViewCursor(view, flipY
						? kLeftBottomRightTopCursor : kLeftTopRightBottomCursor);
				} else {
					_SetViewCursor(view, flipY
						? kLeftTopRightBottomCursor : kLeftBottomRightTopCursor);
				}
				break;
		}
	} else if (rotation < 90.0) {
		switch (fCorner) {
			case LEFT_TOP_CORNER:
			case RIGHT_BOTTOM_CORNER:
				if (flipX) {
					_SetViewCursor(view,
						flipY ? kLeftRightCursor : kUpDownCursor);
				} else {
					_SetViewCursor(view,
						flipY ? kUpDownCursor : kLeftRightCursor);
				}
				break;
			case RIGHT_TOP_CORNER:
			case LEFT_BOTTOM_CORNER:
				if (flipX) {
					_SetViewCursor(view,
						flipY ? kUpDownCursor : kLeftRightCursor);
				} else {
					_SetViewCursor(view,
						flipY ? kLeftRightCursor : kUpDownCursor);
				}
				break;
		}
	} else if (rotation < 135.0) {
		switch (fCorner) {
			case LEFT_TOP_CORNER:
			case RIGHT_BOTTOM_CORNER:
				if (flipX) {
					_SetViewCursor(view, flipY
						? kLeftBottomRightTopCursor : kLeftTopRightBottomCursor);
				} else {
					_SetViewCursor(view, flipY
						? kLeftTopRightBottomCursor : kLeftBottomRightTopCursor);
				}
				break;
			case RIGHT_TOP_CORNER:
			case LEFT_BOTTOM_CORNER:
				if (flipX) {
					_SetViewCursor(view, flipY
						? kLeftTopRightBottomCursor : kLeftBottomRightTopCursor);
				} else {
					_SetViewCursor(view, flipY
						? kLeftBottomRightTopCursor : kLeftTopRightBottomCursor);
				}
				break;
		}
	} else {
		switch (fCorner) {
			case LEFT_TOP_CORNER:
			case RIGHT_BOTTOM_CORNER:
				if (flipX) {
					_SetViewCursor(view,
						flipY ? kUpDownCursor : kLeftRightCursor);
				} else {
					_SetViewCursor(view,
						flipY ? kLeftRightCursor : kUpDownCursor);
				}
				break;
			case RIGHT_TOP_CORNER:
			case LEFT_BOTTOM_CORNER:
				if (flipX) {
					_SetViewCursor(view,
						flipY ? kLeftRightCursor : kUpDownCursor);
				} else {
					_SetViewCursor(view,
						flipY ? kUpDownCursor : kLeftRightCursor);
				}
				break;
		}
	}
}


const char*
DragCornerState::ActionName() const
{
	return B_TRANSLATE("Scale");
}


// #pragma mark - DragSideState


DragSideState::DragSideState(TransformBox* parent, uint32 side)
	:
	DragState(parent),
	fSide(side)
{
}


void
DragSideState::SetOrigin(BPoint origin)
{
	fOldXScale = fParent->LocalXScale();
	fOldYScale = fParent->LocalYScale();

	fOldOffset = fParent->Translation();

	// copy the matrix at the start of the drag procedure
	fMatrix.reset();
	fMatrix.multiply(agg::trans_affine_scaling(fOldXScale, fOldYScale));
	fMatrix.multiply(agg::trans_affine_rotation(fParent->LocalRotation() * M_PI / 180.0));
	fMatrix.multiply(agg::trans_affine_translation(fParent->Translation().x,
		fParent->Translation().y));

	double x = origin.x;
	double y = origin.y;
	fMatrix.inverse_transform(&x, &y);
	origin.x = x;
	origin.y = y;

	BRect box = fParent->Box();
	switch (fSide) {
		case LEFT_SIDE:
			fOffsetFromSide = origin.x - box.left;
			fOldSideDist = box.left - box.right;
			origin.x = box.right;
			break;
		case RIGHT_SIDE:
			fOffsetFromSide = origin.x - box.right;
			fOldSideDist = box.right - box.left;
			origin.x = box.left;
			break;
		case TOP_SIDE:
			fOffsetFromSide = origin.y - box.top;
			fOldSideDist = box.top - box.bottom;
			origin.y = box.bottom;
			break;
		case BOTTOM_SIDE:
			fOffsetFromSide = origin.y - box.bottom;
			fOldSideDist = box.bottom - box.top;
			origin.y = box.top;
			break;
	}
	DragState::SetOrigin(origin);
}


void
DragSideState::DragTo(BPoint current, uint32 modifiers)
{
	double x = current.x;
	double y = current.y;
	fMatrix.inverse_transform(&x, &y);

	double xScale = 1.0;
	double yScale = 1.0;
	BPoint translation(0.0, 0.0);
	switch (fSide) {
		case LEFT_SIDE:
		case RIGHT_SIDE:
			x -= fOrigin.x;
			if (fOldSideDist != 0.0)
				xScale = (x - fOffsetFromSide) / (fOldSideDist);
			translation.x = fOrigin.x - fOrigin.x * xScale;
			break;
		case TOP_SIDE:
		case BOTTOM_SIDE:
			y -= fOrigin.y;
			if (fOldSideDist != 0.0)
				yScale = (y - fOffsetFromSide) / (fOldSideDist);
			translation.y = fOrigin.y - fOrigin.y * yScale;
			break;
	}
	x = translation.x;
	y = translation.y;
	fMatrix.transform(&x, &y);
	translation.x = x;
	translation.y = y;

	fParent->SetTranslationAndScale(translation, xScale * fOldXScale, yScale * fOldYScale);
}


void
DragSideState::UpdateViewCursor(BView* view, BPoint current) const
{
	float rotation = fmod(360.0 - fParent->ViewSpaceRotation() + 22.5, 180.0);
	if (rotation < 45.0) {
		switch (fSide) {
			case LEFT_SIDE:
			case RIGHT_SIDE:
				_SetViewCursor(view, kLeftRightCursor);
				break;
			case TOP_SIDE:
			case BOTTOM_SIDE:
				_SetViewCursor(view, kUpDownCursor);
				break;
		}
	} else if (rotation < 90.0) {
		switch (fSide) {
			case LEFT_SIDE:
			case RIGHT_SIDE:
				_SetViewCursor(view, kLeftBottomRightTopCursor);
				break;
			case TOP_SIDE:
			case BOTTOM_SIDE:
				_SetViewCursor(view, kLeftTopRightBottomCursor);
				break;
		}
	} else if (rotation < 135.0) {
		switch (fSide) {
			case LEFT_SIDE:
			case RIGHT_SIDE:
				_SetViewCursor(view, kUpDownCursor);
				break;
			case TOP_SIDE:
			case BOTTOM_SIDE:
				_SetViewCursor(view, kLeftRightCursor);
				break;
		}
	} else {
		switch (fSide) {
			case LEFT_SIDE:
			case RIGHT_SIDE:
				_SetViewCursor(view, kLeftTopRightBottomCursor);
				break;
			case TOP_SIDE:
			case BOTTOM_SIDE:
				_SetViewCursor(view, kLeftBottomRightTopCursor);
				break;
		}
	}
}


const char*
DragSideState::ActionName() const
{
	return B_TRANSLATE("Scale");
}


// #pragma mark - DragBoxState


void
DragBoxState::SetOrigin(BPoint origin)
{
	fOldTranslation = fParent->Translation();
	DragState::SetOrigin(origin);
}


void
DragBoxState::DragTo(BPoint current, uint32 modifiers)
{
	BPoint offset = current - fOrigin;
	BPoint newTranslation = fOldTranslation + offset;
	if (modifiers & B_SHIFT_KEY) {
		if (fabs(offset.x) > fabs(offset.y))
			newTranslation.y = fOldTranslation.y;
		else
			newTranslation.x = fOldTranslation.x;
	}
	fParent->TranslateBy(newTranslation - fParent->Translation());
}


void
DragBoxState::UpdateViewCursor(BView* view, BPoint current) const
{
	_SetViewCursor(view, kMoveCursor);
}


const char*
DragBoxState::ActionName() const
{
	return B_TRANSLATE("Move");
}


// #pragma mark - RotateBoxState


RotateBoxState::RotateBoxState(TransformBox* parent)
	:
	DragState(parent),
	fOldAngle(0.0)
{
}


void
RotateBoxState::SetOrigin(BPoint origin)
{
	DragState::SetOrigin(origin);
	fOldAngle = fParent->LocalRotation();
}


void
RotateBoxState::DragTo(BPoint current, uint32 modifiers)
{
	double angle = calc_angle(fParent->Center(), fOrigin, current);

	if (modifiers & B_SHIFT_KEY) {
		if (angle < 0.0)
			angle -= 22.5;
		else
			angle += 22.5;
		angle = 45.0 * ((int32)angle / 45);
	}

	double newAngle = fOldAngle + angle;

	fParent->RotateBy(fParent->Center(), newAngle - fParent->LocalRotation());
}


void
RotateBoxState::UpdateViewCursor(BView* view, BPoint current) const
{
	BPoint origin(fParent->Center());
	fParent->TransformToCanvas(origin);
	fParent->TransformToCanvas(current);
	BPoint from = origin + BPoint(sinf(22.5 * 180.0 / M_PI) * 50.0,
		-cosf(22.5 * 180.0 / M_PI) * 50.0);

	float rotation = calc_angle(origin, from, current) + 180.0;

	if (rotation < 45.0) {
		_SetViewCursor(view, kRotateLCursor);
	} else if (rotation < 90.0) {
		_SetViewCursor(view, kRotateLTCursor);
	} else if (rotation < 135.0) {
		_SetViewCursor(view, kRotateTCursor);
	} else if (rotation < 180.0) {
		_SetViewCursor(view, kRotateRTCursor);
	} else if (rotation < 225.0) {
		_SetViewCursor(view, kRotateRCursor);
	} else if (rotation < 270.0) {
		_SetViewCursor(view, kRotateRBCursor);
	} else if (rotation < 315.0) {
		_SetViewCursor(view, kRotateBCursor);
	} else {
		_SetViewCursor(view, kRotateLBCursor);
	}
}


const char*
RotateBoxState::ActionName() const
{
	return B_TRANSLATE("Rotate");
}


// #pragma mark - OffsetCenterState


void
OffsetCenterState::SetOrigin(BPoint origin)
{
	fParent->InverseTransform(&origin);
	DragState::SetOrigin(origin);
}


void
OffsetCenterState::DragTo(BPoint current, uint32 modifiers)
{
	fParent->InverseTransform(&current);
	fParent->OffsetCenter(current - fOrigin);
	fOrigin = current;
}


void
OffsetCenterState::UpdateViewCursor(BView* view, BPoint current) const
{
	_SetViewCursor(view, kPathMoveCursor);
}


const char*
OffsetCenterState::ActionName() const
{
	return B_TRANSLATE("Move pivot");
}
